/******************************************************************
Pixel Projectiles
By Shadowdarke (shadowdarke@hotmail.com) May 2005
Commissioned by DaGoat787

Pixel Projectiles provides a simple interface for customizable
directional projectiles that can fire at any angle, speed, and
duration between updates.

HOW TO USE PIXEL PROJECTILES:
You launch a pixel projectile with the FirePixelProjectile()
proc. It's format is as follows:

FirePixelProjectile(owner, destination, proj_type, weapon)
ARGS:
	owner (atom)	- The atom that launched the projectile, also
						the start point of its trajectory.
	destination		- destination may be an angle in degrees, or
						an atom that the projectile is launched
						towards.
	proj_type		- The type of projectile. This should be a
						type of /obj/sd_px_projectile.
						DEFAULT: proj_type = /obj/sd_px_projectile
	weapon (obj)	- The weapon that fired the projectile. Not
						necessary but could be useful to track.

RETURNS: The projectile launched, if it is still active.

/obj/sd_px_projectile is designed so that you may define your own
subtypes with unique properties. The values and procs that you may
override are described below.

obj/sd_px_projectile
	VARS
		range	- The number of cycles this projectile will fly
					DEFAULT: range = 7

		NO LONGER EXISTS####	speed	- Pixel distance that the projectile moves per cycle
								DEFAULT: speed = 32
		INSTEAD NOW USE STEP_SIZE

		delay	- Number of ticks between update cycles
					DEFAULT: delay = 1
		pixel_x		- Starting/current pixel_x
					DEFAULT: pixel_x = 16
		pixel_y		- Starting/current pixel_y
					DEFAULT: pixel_y = 16
	PROCS
		CheckHit(turf/T)
			Searches T to see if the projectile hit anything there.
			ARGS: T	- turf to check for a hit
			RETURNS: The atom hit or null if no atom in the turf is
				hit.
			DEFAULT: returns T if it is dense, the first dense
				item in T, or null if there are no dense atoms in
				the turf.

		Hit(atom/A)
			Called when the projectile hits atom A
			ARGS: A	- The atom that the projectile hit
			DEFAULT: Reports the hit to the world. You should
				override this proc to reflect what you want to
				happen when this projectile hits something.

				IMPORTANT: Hit() should ALWAYS end by deleting
				the projectile i.e. "del(src)".

		Terminate()
			Called when the projectile reaches the end of its range
			without hitting anything
			DEFAULT: Delete the projectile

The other vars and procs of /obj/sd_px_projectile should not be
tampered with under normal circumstances.

Pixel Projectiles are not intended to be saved. Ideally, your
saving routines should not sned an sd_px_projectile obj to a
savefile. If they are accidentally saved, sd_px_projectiles
automatically delete themselves shortly after loading.

HOW TO MAKE SPREAD SHOTS:
Pixel Projectiles provides the FireSpread() proc for easy spread shots.

FireSpread(owner, destination, proj_type, pellets, spread)
ARGS:
	owner (atom)	- The atom that launched the projectile, also
						the start point of its trajectory.
	destination		- destination may be an angle in degrees, or
						an atom that the projectile is launched
						towards.
	proj_type		- The type of projectile. This should be a
						type of /obj/sd_px_projectile.
						DEFAULT: proj_type = /obj/sd_px_projectile
	pellets			- The number of pellets in this spread shot
						DEFAULT: pellets = 1
	spread			- the maximum angle that each pellet can deviate
						from the base angle. If spread is negative
						then the pellets have random trajectories
						within the range, if it is positive, then
						the pellets are evenly spread to fill the
						entire range.
						DEFAULT: spread = 0

Note: You can allow a margin of error for single shots by calling
FireSpread() with 1 pellet and a negative spread. ;)

******************************************************************/
/******************************************************************
This Library has been updated to handle Pixel Movement.
All credit still goes to Shadowdarke (shadowdarke@hotmail.com)

Update by Ease (annoying_splat@hotmail.com) March 2013
******************************************************************/

proc/FirePixelProjectile(atom/movable/owner, destination, proj_type = \
	/obj/sd_px_projectile, weapon=/obj)
	/* fires a pixel based projectile of type proj_type from owner to
		destination. Destination may be a turf or angle of fire.
		Destination may also be the owner's dir, and needs to be
		entered in the format "dir=[owner.dir]" in place of destination */
	if(!owner) return
	var/obj/sd_px_projectile/P = new proj_type()
	P.owner = owner
	P.loc = P.TurfOf(owner)
	P.pixel_x += owner.pixel_x
	P.pixel_y += owner.pixel_y
	P.weapon=weapon

	if(istext(destination))
		destination=copytext(destination,4)
		if(destination=="4")		//East
			destination=0
		else if(destination=="5")	//Northeast
			destination=45
		else if(destination=="1")	//North
			destination=90
		else if(destination=="9")	//Northwest
			destination=135
		else if(destination=="8") 	//West
			destination=180
		else if(destination=="10") 	//Southwest
			destination=225
		else if(destination=="2") 	//South
			destination=270
		else if(destination=="6") 	//South east
			destination=315
	if(isnum(destination))	// an angle
		P.dx = cos(destination) * P.step_size
		P.dy = sin(destination) * P.step_size

	else if(istype(destination, /atom))
		var
			atom/A = destination
			dx; dy; px_dist
		if(istype(A,/atom/movable))
			dx = (A.x - owner.x) * 32 + A:pixel_x - owner.pixel_x
			dy = (A.y - owner.y) * 32 + A:pixel_y - owner.pixel_y
		else
			dx = (A.x - owner.x) * 32 + A.pixel_x - owner.pixel_x
			dy = (A.y - owner.y) * 32 + A.pixel_y - owner.pixel_y
		px_dist = sqrt(dx * dx + dy * dy)
		if(px_dist)	// unit vector times P.speed
			P.dx = P.step_size * dx / px_dist
			P.dy = P.step_size * dy / px_dist
		else	// owner and target in exact same position
			P.Hit(A)
			return P

	else
		world.log << "Invalid destination: FirePixelProjectile([owner], [destination], \
			[proj_type])"
		del(P)
		return

	if(P)
		spawn(P.delay)	// so this proc can return normally
			while(P)
				P.UpdatePosition()
				//if(P && (alert("[P.x]:[P.cx], [P.y]:[P.cy]",,"Ok", "Delete")=="Delete"))
				//	del(P)
				if(P) sleep(P.delay)

	return P

proc/FireSpread(atom/movable/owner, destination, proj_type = \
	/obj/sd_px_projectile, pellets = 1, spread = 0, weapon=/obj)
	var/base_angle
	if(istext(destination))
		destination=copytext(destination,4)
		if(destination=="4")		 //East
			destination=0
		else if(destination=="5")	 //Northeast
			destination=45
		else if(destination=="1")	 //North
			destination=90
		else if(destination=="9")	 //Northwest
			destination=135
		else if(destination=="8") 	//West
			destination=180
		else if(destination=="10") 	//Southwest
			destination=225
		else if(destination=="2") 	//South
			destination=270
		else if(destination=="6") 	//South east
			destination=315
	if(isnum(destination))
		base_angle = destination

	else if(istype(destination, /atom))
		var
			atom/A = destination
			dx; dy;
		if(istype(A,/atom/movable))
			dx = (A.x - owner.x) * 32 + A:pixel_x - owner.pixel_x
			dy = (A.y - owner.y) * 32 + A:pixel_y - owner.pixel_y
		else
			dx = (A.x - owner.x) * 32 + A.pixel_x - owner.pixel_x
			dy = (A.y - owner.y) * 32 + A.pixel_y - owner.pixel_y
		// Arctan courtesy of Lummox JR
		if(!dx && !dy) base_angle = 0    // the only special case
		else
			var/a=arccos(dx/sqrt(dx*dx+dy*dy))
			base_angle = (dy>=0)?(a):(-a)
	if(spread<=0)	// random spread
		for(var/loop = 1 to pellets)
			FirePixelProjectile(owner, base_angle + rand(spread, -spread), proj_type)
	else if(pellets>1)	// uniform spread
		var/d_angle = spread*2/(pellets-1)
		base_angle -= spread
		for(var/loop = 0 to pellets - 1)
			FirePixelProjectile(owner, base_angle + loop * d_angle, proj_type, weapon)
	else
		FirePixelProjectile(owner, base_angle, proj_type)

obj/sd_px_projectile
	layer = FLY_LAYER
	animate_movement = NO_STEPS
	density = 1			// Needs to be dense for Bump to work.
	step_size = 32	// px per update cycle
	pixel_x = 16
	pixel_y = 16

	bound_width = 4 	// These need to be hard-coded; this is the width of your projectile icon
	bound_height = 4	// These need to be hard-coded; this is the height of your projectile icon

	// If you don't put your projectile icon in the very bottom-left corner you'll want to add bound_x and bound_y vars.
	// These are covered extensively in the manual.

	var/tmp
		weapon		// The weapon that fired this projectile
		range = 7	// number of cycles this projectile will fly
		delay = 1	// ticks between updates

		////////////////////////////////////////////////////////
		// INTERNAL VARS: You shouldn't mess with the vars below
		////////////////////////////////////////////////////////
		dx	// change in cx per update cycle (derived from speed)
		dy	// change in cy per update cycle (derived from speed)
		owner	// who/what the projectile belongs to
		same_turf = 1	// cleared when proj leaves start turf

	Read()
		// somehow ended up saved to a file. delete it
		. = ..()
		spawn(2) del(src)
	Bump(atom/O)
		src.Hit(O)

	proc
/*		dist(atom/A)
			// returns the square of the pixel distance between src and A
			if(!istype(A)) return 0
			var/sx = (A.x - x) * 32 + A.pixel_x - pixel_x
			var/sy = (A.y - y) * 32 + A.pixel_y - pixel_y
			return sx * sx + sy * sy*/

		Hit(atom/A)
			// the projectile just hit A
			// Override to fit into your program
			world << "[owner]'s [src] hit [A] at ([A.x], [A.y])."

			// Hit() should ALWAYS end with the following line:
			del(src)

		sgn(n)
			if(n<0) return -1
			else if(n>0) return 1
			return 0

		Terminate()
			/* overide if you want the missile to do something
				if it reaches range without hitting something */
			del(src)

		TurfOf(atom/A)
			if(istype(A)) return locate(A.x, A.y, A.z)

		UpdatePosition()
			var
				tempX=x
				tempY=y
				tempStepX=pixel_x
				tempStepY=pixel_y
				tmp/delete=0
			tempStepX += dx
			while(tempStepX >= 32)
				tempStepX -= 32
				if(++tempX > world.maxx)
					tempY=world.maxx
					delete=1
					break
			while(tempStepX < 0)
				tempStepX += 32
				if(--tempX < 1)
					tempY=1
					delete=1
					break

			tempStepY += dy
			while(tempStepY >= 32)
				tempStepY -= 32
				if(++tempY > world.maxy)
					tempY=world.maxy
					delete=1
					break
			while(tempStepY < 0)
				tempStepY += 32
				if(--tempY < 1)
					tempY=1
					delete=1
					break

			Move(locate(tempX,tempY,z),0,tempStepX,tempStepY)

			if(--range<=0)
				Terminate()
			if(delete)
				del src

atom/movable
	Cross(obj/sd_px_projectile/O)
		if(istype(O,/obj/sd_px_projectile))
			O.Move(0)
			O.Hit(src)
		else
			.=..()